using System;
using FluentNHibernate.Mapping;
using NHibernateInsight.Tests.Domain;
using NHibernateInsight.Tests.Infrastructure;
using NUnit.Framework;

namespace NHibernateInsight.Tests.Insights.Relations.ManyToMany
{
    /// <summary>
    /// EVERYTHING WORKS AS ON "Saving_the_related_entites_when_entities_are_in_a_bag"
    /// EXCEPT THE TESTS FROM BELOW!
    /// 
    /// When schema is being exported, primary key is now created on both columns in the relation table
    /// </summary>
    [TestFixture]
    public class Saving_the_related_entites_when_entities_are_in_a_set : TestContext
    {
        private class Mappings_of
        {
            public class Product : ClassMap<Domain.Product>
            {
                public Product()
                {
                    Id(x => x.Id)
                        .GeneratedBy.Native();
                    HasManyToMany(x => x.ProspectsSet)
                        .Cascade.SaveUpdate()
                        .Inverse()
                        .AsSet();
                }
            }
            public class Product_not_being_the_inverse_side_of_relation : ClassMap<Domain.Product>
            {
                public Product_not_being_the_inverse_side_of_relation()
                {
                    Id(x => x.Id)
                        .GeneratedBy.Native();
                    HasManyToMany(x => x.ProspectsSet)
                        .Cascade.SaveUpdate()
                        .AsSet();
                }
            }
            public class Product_not_having_cascade_on_relation : ClassMap<Domain.Product>
            {
                public Product_not_having_cascade_on_relation()
                {
                    Id(x => x.Id)
                        .GeneratedBy.Native();
                    HasManyToMany(x => x.ProspectsSet)
                        .Cascade.None()
                        .AsSet();
                }
            }
            public class Product_having_all_cascade : ClassMap<Domain.Product>
            {
                public Product_having_all_cascade()
                {
                    Id(x => x.Id)
                        .GeneratedBy.Native();
                    HasManyToMany(x => x.ProspectsSet)
                        .Cascade.All()
                        .Inverse()
                        .AsSet();
                }
            }
            public class Product_having_delete_cascade : ClassMap<Domain.Product>
            {
                public Product_having_delete_cascade()
                {
                    Id(x => x.Id)
                        .GeneratedBy.Native();
                    HasManyToMany(x => x.ProspectsSet)
                        .Cascade.Delete()
                        .Inverse()
                        .AsSet();
                }
            }
            public class Prospect : ClassMap<Domain.Prospect>
            {
                public Prospect()
                {
                    Id(x => x.Id)
                        .GeneratedBy.Native();
                    HasManyToMany(x => x.ProductsSet)
                        .Cascade.SaveUpdate()
                        .AsSet();
                }
            }
            public class Prospect_not_having_cascade_on_relation : ClassMap<Domain.Prospect>
            {
                public Prospect_not_having_cascade_on_relation()
                {
                    Id(x => x.Id)
                        .GeneratedBy.Native();
                    HasManyToMany(x => x.ProductsSet)
                        .Cascade.None()
                        .AsSet();
                }
            }
            public class Prospect_having_all_cascade : ClassMap<Domain.Prospect>
            {
                public Prospect_having_all_cascade()
                {
                    Id(x => x.Id)
                        .GeneratedBy.Native();
                    HasManyToMany(x => x.ProductsSet)
                        .Cascade.All()
                        .AsSet();
                }
            }
            public class Prospect_having_delete_cascade : ClassMap<Domain.Prospect>
            {
                public Prospect_having_delete_cascade()
                {
                    Id(x => x.Id)
                        .GeneratedBy.Native();
                    HasManyToMany(x => x.ProductsSet)
                        .Cascade.Delete()
                        .AsSet();
                }
            }
            public class Prospect_with_inverse : ClassMap<Domain.Prospect>
            {
                public Prospect_with_inverse()
                {
                    Id(x => x.Id)
                        .GeneratedBy.Native();
                    HasManyToMany(x => x.ProductsSet)
                        .Cascade.SaveUpdate()
                        .Inverse()
                        .AsSet();
                }
            }
        }

        [SetUp]
        public void Given_that()
        {
            PrepareForSql2005ExpressDb();


            HideCreateAndDropDbSqlFromConsole();
            HideCreateAndDropSchemaSqlFromConsole();
        }

        [TearDown]
        public void Cleanup_database()
        {
            Cleanup();
        }

        /// <summary>
        /// Then primary key constraint exception will occur, because NH adds two entries to relation table
        /// </summary>
        [Test]
        public void When_saving_both_Entities_in_relation_without_any_of_them_being_the_inverse_side_of_relationship()
        {
            Init(m => m.FluentMappings
                          .Add<Mappings_of.Product_not_being_the_inverse_side_of_relation>()
                          .Add<Mappings_of.Prospect>());


            var product = new Product();
            var prospect = new Prospect();

            product.ProspectsSet.Add(prospect);
            prospect.ProductsSet.Add(product);

            using (var tran = CurrentSession.BeginTransaction())
            {
                CurrentSession.SaveOrUpdate(product);

                tran.Commit();
            }
        }

        /// <summary>
        /// Then also *ALL* of the existing child entities of that parent entity will be preloaded.
        /// This is because we are using a set, which cannot have any duplicates...as it cannot, it
        /// will first load the existing items, compare to them via .Equals method, and then only add
        /// the new one if its not already present.
        /// 
        /// To avoid this preload of *ALL* child entites (potentially HUGE number!), use BAG as 
        /// collectin set where appropriate!
        /// </summary>
        [Test]
        public void When_adding_new_entities_in_relation_with_an_existing_parent_entity()
        {
            Init(m => m.FluentMappings
                          .Add<Mappings_of.Product>()
                          .Add<Mappings_of.Prospect>());


            Output("Creating the original entity and its children:");

            var product1 = new Product();
            var product2 = new Product();
            var product3 = new Product();

            var prospect = new Prospect();
                prospect.ProductsSet.Add(product1);
                prospect.ProductsSet.Add(product2);
                prospect.ProductsSet.Add(product3);

            using (var tran = CurrentSession.BeginTransaction())
            {
                CurrentSession.SaveOrUpdate(product1);
                CurrentSession.SaveOrUpdate(product2);
                CurrentSession.SaveOrUpdate(product3);

                CurrentSession.SaveOrUpdate(prospect);


                tran.Commit();
            }


            Output("Now load the same entity from db, and add to it a new child and save:");

            // Kill current entity in NH tracking!
            CurrentSession.Evict(prospect);


            // Load it from DB!
            var reLoadedProspect = CurrentSession.Get<Prospect>(prospect.Id);    
                reLoadedProspect.ProductsSet.Add(new Product());

            using (var tran = CurrentSession.BeginTransaction())
            {
                CurrentSession.SaveOrUpdate(reLoadedProspect);

                tran.Commit();
            }
        }

    }
}